
/*-------------------------------------------------------------------------
 *
 * bufmgr.h
 *	  POSTGRES 缓冲区管理器定义.
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/storage/bufmgr.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef BUFMGR_H
#define BUFMGR_H

#include "storage/block.h"
#include "storage/buf.h"
#include "storage/bufpage.h"
#include "storage/relfilenode.h"
#include "utils/relcache.h"
#include "utils/snapmgr.h"

typedef void *Block;

/* GetAccessStrategy() 的可能参数 */
typedef enum BufferAccessStrategyType
{
	BAS_NORMAL,					/* 正常随机访问 */
	BAS_BULKREAD,				/* 大型只读扫描（提示位更新是可以的） */
	BAS_BULKWRITE,				/* 大型多块写入（例如 COPY IN） */
	BAS_VACUUM					/* VACUUM */
} BufferAccessStrategyType;

/* ReadBufferExtended() 的可能模式 */
typedef enum
{
	RBM_NORMAL,					/* 正常读取 */
	RBM_ZERO_AND_LOCK,			/* 不从磁盘读取，调用者将初始化。还锁定页面。 */
	RBM_ZERO_AND_CLEANUP_LOCK,	/* 像 RBM_ZERO_AND_LOCK，但以“清理”模式锁定页面 */
	RBM_ZERO_ON_ERROR,			/* 读取，但在错误时返回一个全零页面 */
	RBM_NORMAL_NO_LOG			/* 在 WAL 重放期间不要记录页面为无效；否则与 RBM_NORMAL 相同 */
} ReadBufferMode;

/*
 * PrefetchBuffer() 返回的类型。
 */
typedef struct PrefetchBufferResult
{
	Buffer		recent_buffer;	/* 如果有效，是命中（需要重新检查！） */
	bool		initiated_io;	/* 如果为 true，是导致异步 I/O 的未命中 */
} PrefetchBufferResult;

/* 向前声明，以避免在这里暴露 buf_internals.h */
struct WritebackContext;

/* 向前声明，以避免在这里包括 smgr.h */
struct SMgrRelationData;

/* 在 globals.c 中... 这复制了 miscadmin.h */
extern PGDLLIMPORT int NBuffers;

/* 在 bufmgr.c 中 */
extern PGDLLIMPORT bool zero_damaged_pages;
extern PGDLLIMPORT int bgwriter_lru_maxpages;
extern PGDLLIMPORT double bgwriter_lru_multiplier;
extern PGDLLIMPORT bool track_io_timing;
extern PGDLLIMPORT int effective_io_concurrency;
extern PGDLLIMPORT int maintenance_io_concurrency;

extern PGDLLIMPORT int checkpoint_flush_after;
extern PGDLLIMPORT int backend_flush_after;
extern PGDLLIMPORT int bgwriter_flush_after;

/* 在 buf_init.c 中 */
extern PGDLLIMPORT char *BufferBlocks;

/* 在 localbuf.c 中 */
extern PGDLLIMPORT int NLocBuffer;
extern PGDLLIMPORT Block *LocalBufferBlockPointers;
extern PGDLLIMPORT int32 *LocalRefCount;

/* 有效_io并发的上限 */
#define MAX_IO_CONCURRENCY 1000

/* 特殊块号用于 ReadBuffer() */
#define P_NEW	InvalidBlockNumber	/* 扩展文件以获取新页 */

/*
 * 缓冲区内容锁模式（LockBuffer() 的模式参数）
 */
#define BUFFER_LOCK_UNLOCK		0
#define BUFFER_LOCK_SHARE		1
#define BUFFER_LOCK_EXCLUSIVE	2

/*
 * 这些例程经常被调用，因此进行了宏化处理。
 */

/*
 * BufferIsValid
 *		当且仅当给定的缓冲区编号有效（作为共享或本地缓冲区）时为真。
 *
 * 注意：很长一段时间内这与 BufferIsPinned 的定义相同，
 * 即如果没有持有缓冲区的引脚，则返回假。
 * 我认为这是不合理的，仅用于掩盖逻辑错误。
 * 代码应该始终知道它是否有缓冲区引用，
 * 独立于引脚状态。
 *
 * 注意：再很长一段时间内，这并不是 BufferIsInvalid() 宏的完全逆操作，
 * 因为它还进行了一致性检查以验证缓冲区编号是否在范围内。
 * 很可能，此宏最初仅 intended 在断言中使用，但其使用已经扩展了很多，
 * 并且即使在未启用断言的构建中进行这些检查的开销也可能很大。
 * 因此，我们现在将范围检查降级为宏内部的断言。
 */
#define BufferIsValid(bufnum) \
( \
	AssertMacro((bufnum) <= NBuffers && (bufnum) >= -NLocBuffer), \
	(bufnum) != InvalidBuffer  \
)

/*
 * BufferGetBlock
 *		返回与缓冲区相关联的磁盘页图像的引用。
 *
 * 注意：
 *		假设缓冲区是有效的。
 */
#define BufferGetBlock(buffer) \
( \
	AssertMacro(BufferIsValid(buffer)), \
	BufferIsLocal(buffer) ? \
		LocalBufferBlockPointers[-(buffer) - 1] \
	: \
		(Block) (BufferBlocks + ((Size) ((buffer) - 1)) * BLCKSZ) \
)

/*
 * BufferGetPageSize
 *		返回缓冲区内的页面大小。
 *
 * 注意：
 *		假设缓冲区是有效的。
 *
 *		缓冲区可以是原始磁盘块，并且不需要包含有效的
 *		（格式化的）磁盘页面。
 */
/* XXX 应该从缓冲区描述符中挖掘出 */
#define BufferGetPageSize(buffer) \
( \
	AssertMacro(BufferIsValid(buffer)), \
	(Size)BLCKSZ \
)

/*
 * BufferGetPage
 *		返回与缓冲区相关联的页面。
 *
 * 当作为扫描的一部分调用此函数时，可能需要对 TestForOldSnapshot() 的邻近调用。有关详细信息，请参见定义。
 */
#define BufferGetPage(buffer) ((Page)BufferGetBlock(buffer))

/*
 * bufmgr.c 中函数的原型
 */
extern PrefetchBufferResult PrefetchSharedBuffer(struct SMgrRelationData *smgr_reln,
												 ForkNumber forkNum,
												 BlockNumber blockNum);
extern PrefetchBufferResult PrefetchBuffer(Relation reln, ForkNumber forkNum,
										   BlockNumber blockNum);
extern bool ReadRecentBuffer(RelFileNode rnode, ForkNumber forkNum,
							 BlockNumber blockNum, Buffer recent_buffer);
extern Buffer ReadBuffer(Relation reln, BlockNumber blockNum);
extern Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum,
								 BlockNumber blockNum, ReadBufferMode mode,
								 BufferAccessStrategy strategy);
extern Buffer ReadBufferWithoutRelcache(RelFileNode rnode,
										ForkNumber forkNum, BlockNumber blockNum,
										ReadBufferMode mode, BufferAccessStrategy strategy,
										bool permanent);
extern void ReleaseBuffer(Buffer buffer);
extern void UnlockReleaseBuffer(Buffer buffer);
extern void MarkBufferDirty(Buffer buffer);
extern void IncrBufferRefCount(Buffer buffer);
extern Buffer ReleaseAndReadBuffer(Buffer buffer, Relation relation,
								   BlockNumber blockNum);

extern void InitBufferPool(void);
extern void InitBufferPoolAccess(void);
extern void AtEOXact_Buffers(bool isCommit);
extern void PrintBufferLeakWarning(Buffer buffer);
extern void CheckPointBuffers(int flags);
extern BlockNumber BufferGetBlockNumber(Buffer buffer);
extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation,
												   ForkNumber forkNum);
extern void FlushOneBuffer(Buffer buffer);
extern void FlushRelationBuffers(Relation rel);
extern void FlushRelationsAllBuffers(struct SMgrRelationData **smgrs, int nrels);
extern void CreateAndCopyRelationData(RelFileNode src_rnode,
									  RelFileNode dst_rnode,
									  bool permanent);
extern void FlushDatabaseBuffers(Oid dbid);
extern void DropRelFileNodeBuffers(struct SMgrRelationData *smgr_reln, ForkNumber *forkNum,
								   int nforks, BlockNumber *firstDelBlock);
extern void DropRelFileNodesAllBuffers(struct SMgrRelationData **smgr_reln, int nnodes);
extern void DropDatabaseBuffers(Oid dbid);

#define RelationGetNumberOfBlocks(reln) \
	RelationGetNumberOfBlocksInFork(reln, MAIN_FORKNUM)

extern bool BufferIsPermanent(Buffer buffer);
extern XLogRecPtr BufferGetLSNAtomic(Buffer buffer);

#ifdef NOT_USED
extern void PrintPinnedBufs(void);
#endif
extern Size BufferShmemSize(void);
extern void BufferGetTag(Buffer buffer, RelFileNode *rnode,
						 ForkNumber *forknum, BlockNumber *blknum);

extern void MarkBufferDirtyHint(Buffer buffer, bool buffer_std);

extern void UnlockBuffers(void);
extern void LockBuffer(Buffer buffer, int mode);
extern bool ConditionalLockBuffer(Buffer buffer);
extern void LockBufferForCleanup(Buffer buffer);
extern bool ConditionalLockBufferForCleanup(Buffer buffer);
extern bool IsBufferCleanupOK(Buffer buffer);
extern bool HoldingBufferPinThatDelaysRecovery(void);

extern void AbortBufferIO(void);

extern void BufmgrCommit(void);
extern bool BgBufferSync(struct WritebackContext *wb_context);

extern void AtProcExit_LocalBuffers(void);

extern void TestForOldSnapshot_impl(Snapshot snapshot, Relation relation);

/* 在 freelist.c 中 */
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);


/* 内联函数 */

/*
 * 尽管此头文件名义上仅用于后端，某些前端程序（如 pg_waldump）也会包括它。
 * 对于即使未使用也会发出静态内联函数的编译器，这会导致未满足的外部引用；
 * 因此，用 #ifndef FRONTEND 隐藏这些。
 */

#ifndef FRONTEND

/*
 * 检查给定快照是否过旧，以安全地从给定表中读取给定页面。
 * 如果是，则抛出“快照过旧”错误。
 *
 * 通常在作为扫描的一部分执行的每个 BufferGetPage() 调用之后
 * 都需要执行此测试。对于修改页面的调用（例如，定位到正确位置以插入新的索引元组或进行清理），则不需要此测试。
 * 在较低级函数的调用已经执行了测试的情况下，也可以省略此测试。
 *
 * 注意，允许 NULL 快照参数，这会导致快速返回而不出现错误；
 * 这支持可以从扫描或索引修改区域调用的调用点。
 *
 * 为了获得最佳性能，将最快和/或最有可能排除页面的测试
 * 保持在前面。
 */
static inline void
TestForOldSnapshot(Snapshot snapshot, Relation relation, Page page)
{
	Assert(relation != NULL);

	if (old_snapshot_threshold >= 0
		&& (snapshot) != NULL
		&& ((snapshot)->snapshot_type == SNAPSHOT_MVCC
			|| (snapshot)->snapshot_type == SNAPSHOT_TOAST)
		&& !XLogRecPtrIsInvalid((snapshot)->lsn)
		&& PageGetLSN(page) > (snapshot)->lsn)
		TestForOldSnapshot_impl(snapshot, relation);
}

#endif							/* FRONTEND */

#endif							/* BUFMGR_H */
